package cn.zyl.demo.common.algorithm;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * AlgMurmurHash类说明: MurmurHash算法,非加密散列函数
 * <p>高运算性能，低碰撞率</p>
 * <p>产生一个32位或128位散列值。 128位，x86和x64版本不会生成相同的值，因为算法针对各自的平台进行了优化</p>
 * <p>已应用到Hadoop、libstdc++、nginx、libmemcached等开源系统</p>
 * <p>Java界中Redis，Memcached，Cassandra，HBase，Lucene都用它</p>
 *
 * @author wsz
 * @version v1.0
 * @date 2020-08-17
 */
public class AlgMurmurHash {
    /**
     * 64位终端，散列在从当前位置到极限的缓冲器中的字节
     * @param data 字节数组
     * @return 缓冲区中字节的32位哈希
     */
    public static Long hash64A(byte[] data) {
        long m = 0xc6a4a7935bd1e995L;
        return hash(data, m, false);
    }
    /**
     * 32位终端，散列在从当前位置到极限的缓冲器中的字节
     * @param data 字节数组
     * @return 缓冲区中字节的32位哈希
     */
    public static int hash32A(byte[] data) {
        int m = 0x5bd1e995;
        return (int)hash(data, m, true);
    }

    /**
     * hash核心操作，32位和64位参数将不一致
     * @param data 字节数组
     * @param m *=，相乘的因子
     * @param type 运算模式，true 32位；false 64位
     * @return long数字
     */
    private static long hash(byte[] data,long m,boolean type){
        //hash种子
        int seed = 0x1234ABCD;
        ByteBuffer buf = ByteBuffer.wrap(data);
        // 保存字节顺序以便以后恢复
        ByteOrder byteOrder = buf.order();
        buf.order(ByteOrder.LITTLE_ENDIAN);
        long h = type ? (seed ^ buf.remaining() ) : (seed ^ (buf.remaining() * m));
        int r, h1, h2, l;
        if(type){
            //32位
            r = 24;h1 = 13;h2 = 15;l = 4;
        }else {
            //64位
            r = h1 = h2 = 47;l = 8;
        }
        long k;
        while (buf.remaining() >= l) {
            k = type ? buf.getInt() : buf.getLong();
            k *= m;
            k ^= k >>> r;
            k *= m;
            h ^= k;
            h *= m;
        }
        if (buf.remaining() > 0) {
            ByteBuffer finish = ByteBuffer.allocate(l).order(ByteOrder.LITTLE_ENDIAN);
            /* 对于big-endian版本，首先使用这个: finish . position(4-buf . remaining()); */
            finish.put(buf).rewind();
            h ^= type ? finish.getInt() : finish.getLong();
            h *= m;
        }
        h ^= h >>> h1;
        h *= m;
        h ^= h >>> h2;
        buf.order(byteOrder);
        return h;
    }
    public static Long hash64A(String key) { return hash64A(key.getBytes()); }
    public static Integer hash32A(String key) { return hash32A(key.getBytes()); }
    /** Long转换成无符号长整型（C中数据类型） */
    public static BigDecimal readUnsignedLong(long value) {
        if (value >= 0) { return new BigDecimal(value); }
        long lowValue = value & Long.MAX_VALUE;
        return BigDecimal.valueOf(lowValue).add(BigDecimal.valueOf(Long.MAX_VALUE)).add(BigDecimal.valueOf(1));
    }
    public static BigDecimal readUnsignedInteger(Integer value) {
        if (value >= 0) { return new BigDecimal(value); }
        int lowValue = value & Integer.MAX_VALUE;
        return BigDecimal.valueOf(lowValue).add(BigDecimal.valueOf(Integer.MAX_VALUE)).add(BigDecimal.valueOf(1));
    }
    /** 返回无符号murmur hash值 */
    public static BigDecimal hash64Unsigned(String key) { return readUnsignedLong(hash64A(key)); }
    /** 返回无符号murmur hash值 */
    public static BigDecimal hash64Unsigned(byte[] key) { return readUnsignedLong(hash64A(key)); }
    /** 返回无符号murmur hash值 */
    public static BigDecimal hash32Unsigned(String key) { return readUnsignedInteger(hash32A(key)); }
    /** 返回无符号murmur hash值 */
    public static BigDecimal hash32Unsigned(byte[] key) { return readUnsignedInteger(hash32A(key)); }

    public static void main(String[] args) {
        try {
            String test = "测试汉字";
            //1146745369200541601，中间 1146745369200541601
            System.out.println(test+"[64]："+ hash64Unsigned(test));
            //2816248132
            System.out.println(test+"[32]："+ hash32Unsigned(test));
            test = "1234566大大21";
            //10129762727109086067，中间 -8316981346600465549
            System.out.println(test+"[gbk-64]："+ hash64Unsigned(test.getBytes("GBK")));
            //3253097934
            System.out.println(test+"[gbk-32]："+ hash32Unsigned(test.getBytes("GBK")));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}
